<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>手游开发杂谈</title>
    <style>
      @import url(https://fonts.googleapis.com/css?family=Ubuntu);
      @import url(https://fonts.googleapis.com/css?family=Yanone+Kaffeesatz);
      @import url(https://fonts.googleapis.com/css?family=Ubuntu+Mono:400,700,400italic);

      body {
        font-family: 'Ubuntu';
      }
      h1, h2, h3 {
        font-family: 'Yanone Kaffeesatz';
        font-weight: 400;
        margin-bottom: 0;
      }
      .remark-slide-content h1 { font-size: 3.2em; }
      .remark-slide-content h2 { font-size: 2.4em; }
      .remark-slide-content h3 { font-size: 1.6em; }

      .footnote {
        position: absolute;
        bottom: 1em;
      }

      .linknode {
        position: absolute;
        bottom: 1em;
        left: 18em;
        font-size: 0.68em;
      }

      li { line-height: 1.8em; }
      li p { line-height: 1.25em; }
      .red { color: #fa0000; }
      .large { font-size: 2em; }
      a, a > code {
        /*color: rgb(249, 38, 114);*/
        color: rgb(68, 168, 255);
        text-decoration: none;
      }
      code {
        background: #e7e8e2;
        border-radius: 5px;
      }
      .remark-code, .remark-inline-code { 
        font-family: 'Ubuntu Mono';
        font-size: 0.85em;
        line-height: 1.2em;
        max-height: 20em;
      }
      .remark-code-line-highlighted     { background-color: #373832; }
      .pull-left {
        float: left;
        width: 47%;
      }
      .pull-right {
        float: right;
        width: 47%;
      }
      .pull-right ~ p {
        clear: both;
      }
      #slideshow .slide .content code {
        font-size: 0.8em;
      }
      #slideshow .slide .content pre code {
        font-size: 0.9em;
        padding: 15px;
      }
      .inverse {
        background: #272822;
        color: #777872;
        text-shadow: 0 0 20px #333;
      }
      .inverse h1, .inverse h2 {
        color: #f3f3f3;
        line-height: 0.8em;
      }

      /* Slide-specific styling */
      #slide-inverse .footnote {
        bottom: 12px;
        left: 20px;
      }
      #slide-how .slides {
        font-size: 0.9em;
        position: absolute;
        top:  151px;
        right: 140px;
      }
      #slide-how .slides h3 {
        margin-top: 0.2em;
      }
      #slide-how .slides .first, #slide-how .slides .second {
        padding: 1px 20px;
        height: 90px;
        width: 120px;
        -moz-box-shadow: 0 0 10px #777;
        -webkit-box-shadow: 0 0 10px #777;
        box-shadow: 0 0 10px #777;
      }
      #slide-how .slides .first {
        background: #fff;
        position: absolute;
        top: 20%;
        left: 20%;
        z-index: 1;
      }
      #slide-how .slides .second {
        position: relative;
        background: #fff;
        z-index: 0;
      }

      /* Two-column layout */
      .left-column {
        color: #777;
        width: 20%;
        height: 92%;
        float: left;
      }
      .left-column h2:last-of-type, .left-column h3:last-child {
        color: #000;
      }
      .right-column {
        width: 75%;
        float: right;
        padding-top: 1em;
      }

      .detail {
        width: 75%;
        float: right;
        left: 5em;
        text-align: left;
      }
      .main-detail h3 {
        color: #f3f3f3;
      }

    </style>
  </head>
  <body>
    <textarea id="source">
name: inverse
layout: true
class: center, middle, inverse

---

# H5 mobile game development

[H5手游开发经验分享]

.footnote[Go directly to [project site](https://gitee.com/codesprite/H5-Game-Develop)]

---

## from HTML4 to HTML5
H5手游起源

.footnote[Go directly to [project site](https://gitee.com/codesprite/Sharing)]

---

layout: false

## H5手游起源

.left-column[

### HTML4

]

.right-column[
  
### Hyper Text Markup Language

* 表示信息，类似XML
* DOM模式
  * 描述内容的结构
  * 天生就慢
  * CSS，JS，浏览器差异大
  * 很难表现游戏

]

---

## H5手游起源

.left-column[

### HTML4
### Flash

]

.right-column[

### Flash的天下

* 强大好用的动画能力
* 丰富的交互性
* 跨平台表现一致
* 无法解决的虚拟机性能问题

]

---

## H5手游起源

.left-column[

### HTML4
### Flash
### HTML5

]

.right-column[

### 为实现互联网App而设计

* 增加canvas，webGL等支持
* 增加音视频播放支持
* 本地存储和本地数据库等

]

.linknode[

* [H5新增内容](https://leohxj.gitbooks.io/front-end-database/html-and-css-basic/what-is-html5.html)
* [HTML5](https://developer.mozilla.org/zh-CN/docs/Web/Guide/HTML/HTML5)

]

---

template: inverse

## Special
H5手游的独特之处

.footnote[Go directly to [project site](https://gitee.com/codesprite/Sharing)]

---

template: inverse

## No DOM, CSS, Only Resources and JS
和传统手游开发没区别

.footnote[Go directly to [project site](https://gitee.com/codesprite/Sharing)]

---

template: inverse

## Always online
强联网，无单机

.footnote[Go directly to [project site](https://gitee.com/codesprite/Sharing)]

---

## Always online

.left-column[

### 分发方式

]

.right-column[

* 一个链接，即点即玩，玩完即走
* 脱离设备，玩家数据存储与网络

]

---

## Always online

.left-column[

### 分发方式
### 更新方式

]

.right-column[

* 灵活，透明
* 资源以很小的粒度从CDN下载，依托MD5等策略，能做到随时无感热更新
* 利用浏览器的缓存机制，加载效率相对App并无劣势

]

---

## Always online

.left-column[

### 分发方式
### 更新方式
### 重玩法和社交

]

.right-column[

* 性能强，加载快，这些是手游App的市场，不是H5的战场
* 社交分享/合作/比拼，才是发挥H5优势的场景

]

---

template: inverse

## Always online
单机H5游戏的需求几乎不存在了

.footnote[Go directly to [project site](https://gitee.com/codesprite/Sharing)]

---

template: inverse

## A thousand different containers
容器差别很大

.footnote[Go directly to [project site](https://gitee.com/codesprite/Sharing)]

---

## Different Containers

.left-column[

### 容器太多

]

.right-column[

* QQ内建浏览器（性能极低）
* QQ玩一玩
* 微信内建浏览器
* 微信小游戏
  * 6.7.2版本，只能手动外部更新，无法从6.6.7内部更新
  * 6.7.2版本性能提升明显，websocket能提升一倍多性能，渲染性能也有提升
* 成百上千的浏览器产品

]

---

## Different Containers

.left-column[

### 容器太多
### 兼容性差

]

.right-column[

* 性能取决于容器
* 能力取决于容器
  * worker
  * webGL
  * api支持（生成图像，eval...）
* DOM，BOM，兼容性差别很大

]

---

template: inverse

## LayaBox
引擎介绍

.footnote[Go directly to [project site](https://gitee.com/codesprite/Sharing)]

---

## LayaBox

.left-column[

### 性能强

]

.right-column[

* 深度优化的渲染管线
* 自动批次合并
* 自动纹理合图
* 缓存渲染指令或纹理
* 内存开销极低
  * 大量使用对象池，减少垃圾回收（垃圾回收不可控，必卡）
  * 对象内存占用极低（sprite创建只消耗116字节）
  * 运行时0消耗（5000个sprite运动，旋转，缩放，运行过程无消耗）
  * graphics可以绘制多次，减少节点开销

]

---

## LayaBox

.left-column[

### 性能强
### 开源简单

]

.right-column[

* 统一touch和mouse事件
* 丰富的事件类型
* event对象可以带任意参数

]

---

## LayaBox

.left-column[

### 性能强
### 开源简单
### 够用好用

]

.right-column[

* 统一的资源管理器
  * 多线程加载
  * 加载排重
  * 统一的获取和销毁接口

]

---

## LayaBox

.left-column[

### 性能强
### 开源简单
### 够用好用
### 2.0

]

.right-column[

* 组件化开发
* 可视化物理系统
* 基于场景的内存和资源管理
* 3D能力增强
* layaCloud后端框架

]

---

template: inverse

## WeChat Game
微信小游戏

.footnote[Go directly to [project site](https://gitee.com/codesprite/Sharing)]

---

template: inverse

## very special, very different
这是一个非常特殊的宿主环境，和浏览器差异非常多

.footnote[Go directly to [project site](https://gitee.com/codesprite/Sharing)]

---

## Different

.left-column[

### 非浏览器

]

.right-column[

* 没有BOM，DOM
  * 没有全局的document，window，全局变量要注意
  * 引擎实现了adapter
* 图片资源有缓存，以md5作为文件名，向子域透传只能通过微信接口获取本地文件名
* websocket只支持string/arrayBuffer，protobuf使用的uint8Array需要转换
```javascript
// request是UInt8Buffer，微信要求是ArrayBuffer
request = request.buffer.slice(
    request.byteOffset,
    request.byteOffset + request.byteLength
);
```
* 网络请求必须使用具有有效证书的https或wss
  * 必须使用备案的域名并且填写到小游戏后台
  * 真机调试需要再后台配置好才能访问

]

---

## Different

.left-column[

### 非浏览器
### 包限制

]

.right-column[

* 包大小限制：不分包4M，分包8M
* 文件类型限制
* 统一使用CDN，统一资源接口

]

.linknode[

* [文件类型限制](https://developers.weixin.qq.com/minigame/dev/)

]

---

## Different

.left-column[

### 非浏览器
### 包限制
### 适配

]

.right-column[

* 默认拉伸充满整个屏幕，变形
* laya的适配居中和画布居中对齐等参数在微信下都是无效的
* 必须使用fixed模式，fixedWidth，fixedHeight，fixedAuto

```javascript
// 屏幕适配 选择适配方式
Laya.init(640, 1136, Laya.WebGL);

Laya.stage.screenMode = Laya.Stage.SCREEN_VERTICAL;
Laya.stage.alignH = Laya.Stage.ALIGN_CENTER;
Laya.stage.alignV = Laya.Stage.ALIGN_MIDDLE;

if (Laya.Browser.clientHeight / Laya.Browser.clientWidth < 16.0 / 10.0) {
    Laya.stage.scaleMode = Laya.Stage.SCALE_FIXED_AUTO;
} else {
    Laya.stage.scaleMode = Laya.Stage.SCALE_FIXED_WIDTH;
}
```

]

---

## Open Data Area

.left-column[

### 概述

]

.right-column[

* 一个只进不出的封闭运行环境
* 只能请求微信服务器
* 可以获取微信关系链
* 可以获取托管到微信的keyValue数据
* 与主域公用一个sharedCanvas
  * canvas大小必须一致，否则触摸会乱
  * 只支持canvas渲染，目前实现不了透明度，未来或支持

]

---

## Open Data Area

.left-column[

### 概述
### 透传资源

]

.right-column[

* 利用Loader处于引擎底层的特性
* atlas（图集描述，JSON格式）直接透传JS对象
* png
  * 主域下载图片
  * 通过wx.downloadFile接口拿到临时文件路径
  * 子域加载临时文件，并修复资源对应关系
* ArrayBuffer，转成字符串传进去再转回来

]

---

## Open Data Area

.left-column[

### 概述
### 透传资源

]

.right-column[

* 主域下载完资源之后，传给子域

```javascript
WeixinUtils.loadFileInOpenData = function(file) {
    var url = Laya.URL.formatURL(file);
    var fileSplits = file.split('.');
    var ext = fileSplits[fileSplits.length - 1];

    if (ext === 'png' || ext === 'PNG') {
        wx.downloadFile({
            url: url,
            success: function(data) {
                console.log('weixin downloaded');
                console.log(data);
                wx.postMessage({
                    command: 'loadFile',
                    data: {
                        tempUrl: data.tempFilePath,
                        file: file
                    }
                });
            }
        });
    } else if (ext === 'fui') {
        var data = Laya.loader.getRes(file);
        var dataStr = String.fromCharCode.apply(null, new Uint8Array(data));
        wx.postMessage({
            command: 'loadFile',
            data: {
                file: file,
                data: dataStr
            }
        });
    } else if (ext === 'ani' || ext === 'atlas') {
```

]

---

## Open Data Area

.left-column[

### 概述
### 透传资源

]

.right-column[

* 子域接收消息并处理资源

```javascript
var fileSplits = msg.file.split('.');
var ext = fileSplits[fileSplits.length - 1];

if (ext === 'png' || ext === 'PNG') {
    Laya.loader.load(msg.tempUrl, Handler.create(null, function () {
        Laya.Loader.loadedMap[Laya.URL.formatURL(msg.file)] = Laya.loader.getRes(msg.tempUrl);
    }));
} else if (ext === 'fui') {
    var dataStr = msg.data;
    var buffer = new ArrayBuffer(dataStr.length);
    var bufView = new Uint8Array(buffer);
    var strLen=dataStr.length;
    for (var i = 0; i < strLen; i++) {
        bufView[i] = dataStr.charCodeAt(i);
    }

    Laya.Loader.loadedMap[Laya.URL.formatURL(msg.file)] = buffer;
} else if(ext === 'ani' || ext === 'atlas') {
```

]

---

template: inverse

## About the Optimization
关于优化

.footnote[Go directly to [project site](https://gitee.com/codesprite/Sharing)]

---

template: inverse

## A higher level than coding
编程以外的思考

.footnote[Go directly to [project site](https://gitee.com/codesprite/Sharing)]

---

template: inverse

.main-detail[

### 游戏是个快速试错的行业，成功率极低
### 权衡{开发效率}与{性能/设计}之间的关系
### 优化是开发人员对自己负责

.detail[

* 通过良好的设计减轻功能扩展和修改带来的工作量
* 通过良好的设计减少bug的产生
* 优化自己的工作时间

]

]

.footnote[Go directly to [project site](https://gitee.com/codesprite/Sharing)]

---

template: inverse

## When to optimize
什么时候优化

.footnote[Go directly to [project site](https://gitee.com/codesprite/Sharing)]

---

template: inverse

.main-detail[

### 设计程序结构时
多用点儿时间，非常值得

### 出现性能问题时
针对性的优化短板

### 不要过度优化
有1万块的时候，不要纠结花10块还是花100

]

.footnote[Go directly to [project site](https://gitee.com/codesprite/Sharing)]

---

template: inverse

## Main optimization points
主要优化点

.footnote[[手游优化简述](https://gitee.com/codesprite/Sharing/blob/master/%E6%89%8B%E6%B8%B8%E4%BC%98%E5%8C%96%E7%AE%80%E8%BF%B0.md)]

---

## Main optimization points

.left-column[

### 查找问题

]

.right-column[

* chrome的性能调试工具
* 写测试代码，排除主观因素

]

---

## Main optimization points

.left-column[

### 查找问题
### 渲染

]

.right-column[

* 有自动批次和自动合图，省了很多优化的工作
* 主要是drawCall和顶点
* 调整节点顺序
* 使用graphics绘制多个纹理，来减少内存开销和渲染节点数

]

---

## Main optimization points

.left-column[

### 查找问题
### 渲染
### IO

]

.right-column[

* worker
* 尽量减少JSON编解码
* 使用loading
* 异步加载

]

.linknode[

* [worker](https://ldc.layabox.com/doc/?nav=zh-js-3-1-1)

]

---

## Main optimization points

.left-column[

### 查找问题
### 渲染
### IO
### 其他优化

]

.right-column[

* 用对象池减少垃圾回收
  * 前期就用起来
  * 涉及对象的重置，会影响设计
* 使用局部变量减少字典查询
* 脏数据模式减少运算开销
* 碰撞检测优化
  * 基于位运算的网格
  * 使用圆做碰撞检测
* 逻辑结构，开发快，好维护
* 资源优化
  * gzip
  * png压缩，pngquant

]

---

template: inverse

## Development aid
开发辅助工具

.footnote[Go directly to [project site](https://gitee.com/codesprite/Sharing)]

---

template: inverse

.main-detail[

### 通过Python脚本，更精细的优化开发流程

.detail[

* 自动注入版本控制，解决缓存更新问题
* 文件监控
  * 自动编译
  * 自动优化png
  * 自动生成md5文件
  * 自动发布微信小游戏调试
* 开启gzip
* 代码区分模式
  * develop模式
  * release模式
  * 渠道

]

]


.footnote[Go directly to [project site](https://gitee.com/codesprite/Sharing)]

---

name: last-page
template: inverse

## That's all folks (for now)!

![qrcode](qrcode.svg)

Slideshow created using [remark](http://github.com/gnab/remark).

    </textarea>
    <script src="https://remarkjs.com/downloads/remark-latest.min.js"></script>
    <script>
      var slideshow = remark.create({
          highlightStyle: 'monokai',
          highlightLanguage: 'remark',
          highlightLines: true,

          navigation: {
            scroll: false,
            touch: true,
            click: false
          }
        }) ;
    </script>
  </body>
</html>

